package com.issac.messagequeue.mqserver.mqclient;

import com.issac.messagequeue.mqserver.common.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 表示一个TCP连接
 *  持有Socket对象
 *  写入请求/读取响应
 *  管理多个Channel对象
 */
@Data
@Slf4j
public class Connection {
    private Socket socket = null;
    private ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();
    //                       channelId, Channel

    private InputStream inputStream;
    private OutputStream outputStream;
    private DataInputStream dataInputStream;
    private DataOutputStream dataOutputStream;

    private ExecutorService callbackPool = null;

    public Connection(String host, int port) throws IOException {
        socket = new Socket(host, port);
        inputStream = socket.getInputStream();
        outputStream = socket.getOutputStream();
        dataInputStream = new DataInputStream(inputStream);
        dataOutputStream = new DataOutputStream(outputStream);

        callbackPool = Executors.newFixedThreadPool(4);

        // 创建一个扫描线程：负责不停的从socket中读取响应数据，把这个响应数据交给对应的channel处理
        Thread t = new Thread(() -> {
            try {
                while (!socket.isClosed()) {
                    // 1. 读取响应
                    Response response = readResponse();
                    // 2. 响应数据提交给对应的channel
                    dispatchResponse(response);
                }
            } catch (SocketException e) {
                // 这个情况属于：连接正常断开
                log.info("[Connection] 连接正常断开！");
            } catch (IOException | ClassNotFoundException | MqException e) {
                log.error("[Connection] 连接异常断开！");
                e.printStackTrace();
            }
        });
        t.start();
    }

    // 关闭Connection，释放上述资源
    public void close() {
        try {
            callbackPool.shutdownNow();
            channelMap.clear();
            inputStream.close();
            outputStream.close();
            socket.close();
        } catch (IOException e) {
            log.info("[Connection] 资源释放完毕！");
            e.printStackTrace();
        }
    }

    // 需要处理：
    //  1. 当前响应是一个针对控制请求的响应
    //  2. or 服务器推送的消息
    private void dispatchResponse(Response response) throws IOException, ClassNotFoundException, MqException {
        if (response.getType() == 0xc) {
            // 服务器推送来的消息数据
            SubscribeReturns subscribeReturns = (SubscribeReturns) BinaryTool.fromBytes(response.getPayload());
            // 根据channelId找到对应channel对象
            Channel channel = channelMap.get(subscribeReturns.getChannelId());
            if (channel == null) {
                throw new MqException("[Connection] 该消息对应的 channel 在客户端不存在！ channelId=" + channel.getChannelId());
            }
            // 执行该 channel 对象内部回调
            callbackPool.submit(() -> {
               try {
                   channel.getConsumer().handleDelivery(subscribeReturns.getConsumerTag(),
                           subscribeReturns.getBasicProperties(), subscribeReturns.getBody());
               } catch (MqException | IOException e) {
                   log.error("[Connection] 分发响应时，执行回调出错！ channelId=" + channel.getChannelId());
                   e.printStackTrace();
               }
            });
        } else {
            // 当前想用针对刚才的控制请求的响应
            BasicReturns basicReturns = (BasicReturns) BinaryTool.fromBytes(response.getPayload());
            // 把returns放到对应的channel的map中
            Channel channel = channelMap.get(basicReturns.getChannelId());
            if (channel == null) {
                throw new MqException("[Connection] 该消息对应的 channel 在客户端中不存在！ channelId=" + channel.getChannelId());
            }
            channel.putReturns(basicReturns);
        }
    }

    // 读取响应
    private Response readResponse() throws IOException {
        Response response = new Response();
        response.setType(dataInputStream.readInt());
        response.setLength(dataInputStream.readInt());
        byte[] payload = new byte[response.getLength()];
        int payloadLength = dataInputStream.read(payload);
        if (payloadLength != response.getLength()) {
            throw new IOException("[Connection] 读取的响应数据不完整！");
        }
        response.setPayload(payload);
        log.info("[Connection] 收到响应！ type=" + response.getType() + ", length=" + response.getLength());
        return response;
    }

    // 发送请求
    public void writeRequest(Request request) throws IOException {
        dataOutputStream.writeInt(request.getType());
        dataOutputStream.writeInt(request.getLength());
        dataOutputStream.write(request.getPayload());
        dataOutputStream.flush();
        log.info("[Connection] 发送请求！ type=" + request.getType() + ", length=" + request.getLength());
    }

    // 在 Connection 中创建出一个 Channel
    public Channel createChannel() throws IOException {
        String channelId = "C-" + UUID.randomUUID().toString();
        Channel channel = new Channel(channelId, this);
        // 把channel对象加入到管理channel的hash表中
        channelMap.put(channelId, channel);
        // 把创建channel消息告诉服务器
        boolean result = channel.createChannel();
        if (!result) {
            channelMap.remove(channelId);
            return null;
        }
        return channel;
    }
}
